#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Noah-predictor.h"
#include "real-page-predictor.h"
#include "predict_evaluation.h"

//每一行的最大字节数，同时也是行缓冲区的大小
#define MAX_LINE_SIZE 1024

//所以我们一行行读出来，然后使用逗号分隔开
//将块大小设定为4K
#define PAGE_SIZE 4096

//noah预测深度的最大值
#define MAX_NOAH_PREDICT_DEEP 64

//real_page预测的最大深度
#define MAX_PAGE_PREDICT_DEEP 8

//因为数据集很大所以需要限定进行测试的数据集个数



//初始化realpage预测器，当noah不起作用的时候使用
void init_page_predictor(page_predictor_t* page_predictor){
    page_predictor->last_access = -1;
    page_predictor->predictor_size = 2;
}

//这里更新real_page预测器元数据
void real_page_update(page_predictor_t* page_predictor, long now_access){
    //printf("开始进行page数据更新，now_access = %ld\n", now_access);

    //判定访问是不是连续来决定预读的
    if(page_predictor->last_access + 1 == now_access){
        if(page_predictor->predictor_size < MAX_PAGE_PREDICT_DEEP){
            //如果发现是连续的，并且预读窗口的大小还是没有到极限，那么就扩大预读窗口
            if(page_predictor->predictor_size == 0){
                page_predictor->predictor_size = 2;
            }else{
                page_predictor->predictor_size = page_predictor->predictor_size * 2;
            }
        }
    }else{
        //如果发现并不是连续访问，那就关闭预读窗口
        page_predictor->predictor_size = 0;
    }

    page_predictor->last_access = now_access;
}

//这里使用real_page预测器进行预测
void real_page_predict(page_predictor_t* page_predictor, long *predictor_arr, int *size){
    if(predictor_arr == NULL){
        printf("predictor_arr必须在外部分配好空间、\n");
        return;
    }

    int i;

    //根据当前预读窗口进行预测
    *size = page_predictor->predictor_size;

    for(i = 0; i < *size; i++){
        //开始检查上一次预测的记录
        predictor_arr[i] = page_predictor->last_access + i + 1;
    }
}

//这里进行预测，传入预测器指针，当前访问的page块，要传出的预测序列指针以及访问序列的长度
void real_page_predictor(page_predictor_t* page_predictor, long now_access, long *predictor_arr, int *size){
    if(predictor_arr == NULL){
        printf("predictor_arr必须在外部分配好空间、\n");
        return;
    }

    int i;

    if(page_predictor->last_access + 1 == now_access){
        if(page_predictor->predictor_size < MAX_PAGE_PREDICT_DEEP){
            page_predictor->predictor_size = page_predictor->predictor_size * 2;
        }

        *size = page_predictor->predictor_size;
    }else{
        page_predictor->predictor_size = 2;
        *size = 0;
    }

    for(i = 0; i < *size; i++){
        //开始检查上一次预测的记录
        predictor_arr[i] = now_access + i + 1;
    }

    page_predictor->last_access = now_access;
}

//这个程序就是在混合数据集中使用noah预测器，看看这种预测器来混合数据集中的表现
int main(){
    //首先从数据集中读取数据
    //从文件中一个个读入
    FILE *fp_read = NULL;
    char line[MAX_LINE_SIZE];
    long access_block;
    int access_size;

    //用来进行循环迭代的变量
    int i;

    //记录总共多少行数据
    long record_count = 0;

    //这里记录顺序访问的数量
    long seq_access_count = 0;

    //这里记录noah被触发的数量
    long noah_active_count = 0;

    //总共访问的数据块数量
    long data_size = 0;

    // //这里加入一个读缓存
    // all_read_cache_t read_caches;
    // //初始化读缓存
    // init_readcache_meta(&read_caches);

    //不使用读缓存，而是使用一个预测情况评价器predict_evaluation

    //上一次访问的数据块记录
    long last_access_block = -1;
    int last_access_size = -1;

    //这里初始化一个预测序列，noah预测器和page预测器共用一个预测序列，因为二者不会被同时使用
    long* predict_seq;
    //预测的大小
    int size;
    //初始化一个预测序列
    predict_seq = (long*)malloc(MAX_NOAH_PREDICT_DEEP*sizeof(long));
    memset(predict_seq,0,MAX_NOAH_PREDICT_DEEP*sizeof(long));


    //初始化noah
    Noah_predictor_t noah;
    Noah_predictor_init(&noah, 2);

    //初始化real_page
    page_predictor_t real_page;
    init_page_predictor(&real_page);

    //初始化一个评价器
    predict_evaluation_t predict_evaluation;
    init_predict_evaluation(&predict_evaluation);

    printf("开始运行预测\n");

    //将line中的数据分割为访问地址和访问大小，这里为分割做准备
    char delims[] = ",";
    char *result = NULL;

    if((fp_read = fopen("./resource/fix-tracecsv-20180628", "r"))!=NULL){
        printf("开始读取访问的历史记录\n");

        //开始遍历所有的访问序列
        while(fgets(line, MAX_LINE_SIZE, fp_read)){
            //将每一行内容读入
            record_count++;

            if(record_count % 10000 == 0){
                printf("正在读取第%ld个数据\n", record_count);
            }

            //这里是个分割的元素
            result = strtok( line, delims );

            //记录历史的访问块号
            last_access_block = access_block;
            last_access_size = access_size;

            //这里得出访问的块号
            access_block = atol(result) / PAGE_SIZE;

            //这里继续分割一个元素
            result = strtok( NULL, delims );

            access_size = atoi(result)/PAGE_SIZE;

            data_size = access_size + data_size;

            // printf("%ld,%d\n", access_block,access_size);

            //这里更新noah预测器以及read_page预测器，并且更新预测评价器
            for(i = 0; i < access_size; i++){
                //将数据按照4k拆开，然后分开更新数据
                noah_predictor(&noah, access_block+i, predict_seq, &size);
                // noah_predictor_update(&noah, access_block+i);
                real_page_update(&real_page, access_block+i);

                //更新预测结果评价统计
                update_predict_evaluation(&predict_evaluation, access_block+i);
            }

            //这里得出预测结果，相当于在一次请求之后，得出对应的预测请求
            

            //我们不限定预读触发的场景，因为noah只在小粒度顺序访问的时候进行元数据收集，所以
            // if(last_access_block + last_access_size == access_block){
            //这里就认为是连续的访问，进行noah预测
            // memset(predict_seq,0,MAX_NOAH_PREDICT_DEEP*sizeof(long));
            //从单次访问的最后一次开始预测
            seq_access_count++;

            //如果noah在这一轮中没有触发，那么触发real_page_update
            if(size != 0){
                noah_active_count++;
                // printf("noah预读结果:");
                // //查看获取的预读结果
                // for(i = 0; i < size; i++){
                //     printf("%ld,",predict_seq[i]);
                // }
                // printf("\n");
            }else{
                //这里说明noah没有被触发，那么就使用real_page的结果
                real_page_predict(&real_page, predict_seq, &size);
            }

            //将real_page的结果写入预测器
            // real_page_predict(&real_page, predict_seq, &size);

            //将预测结果写入新的评判器
            for(i = 0; i < size; i++){
                insert_predict_history(&predict_evaluation, predict_seq[i]);
            }
            
            // }

            //提前截止
            if(record_count  == 30000){
                break;
            }

        }
    }else{
        printf("文件开启失败\n");
        return 0;
    }

    //这里打印总共弹出的数量和错误的数量以及预测触发的数量，以及顺序访问的数量s
    printf("弹出历史记录的数量：%ld\n", predict_evaluation.history_count);
    printf("错误预测的数量：%ld\n", predict_evaluation.err_count);
    printf("noah触发的数量：%ld\n", noah_active_count);
    printf("顺序访问的数量：%ld\n", seq_access_count);
    printf("总访问数据块的数量：%ld\n", data_size);
    

    //关闭
    printf("总共读取的历史记录个数：%ld\n",record_count);
    fclose(fp_read);
    fp_read = NULL;

    return 0;
}